package org.lex.swing.util;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.LayoutManager;

public class ButtonLayout implements LayoutManager, java.io.Serializable {
	public static final int CENTER = FlowLayout.CENTER;
	public static final int LEADING = FlowLayout.LEADING;
	public static final int TRAILING = FlowLayout.TRAILING;
	private static final long serialVersionUID = -7262534875583282631L;
	private int align;
	private int hgap;
	private int vgap;

	public ButtonLayout() {
		this(CENTER, 5, 5);
	}

	public ButtonLayout(int align) {
		this(align, 5, 5);
	}

	public ButtonLayout(int align, int hgap, int vgap) {
		this.align = align;
		this.hgap = hgap;
		this.vgap = vgap;
	}

	public int getAlignment() {
		return align;
	}

	public void setAlignment(int align) {
		this.align = align;
	}

	public int getHgap() {
		return hgap;
	}

	public void setHgap(int hgap) {
		this.hgap = hgap;
	}

	public int getVgap() {
		return vgap;
	}

	public void setVgap(int vgap) {
		this.vgap = vgap;
	}

	public void addLayoutComponent(String name, Component comp) {
	}

	public void removeLayoutComponent(Component comp) {
	}

	/**
	 * Returns the preferred dimensions for this layout given the <i>visible</i>
	 * components in the specified target container.
	 * 
	 * @param target
	 *            the container that needs to be laid out
	 * @return the preferred dimensions to lay out the subcomponents of the
	 *         specified container
	 */
	public Dimension preferredLayoutSize(Container target) {
		synchronized (target.getTreeLock()) {
			Dimension dim = new Dimension(0, 0);
			int childCount = target.getComponentCount();
			boolean firstVisibleComponent = true;
			int maxAscent = 0;
			int maxDescent = 0;
			for (int i = 0; i < childCount; i++) {
				Component m = target.getComponent(i);
				if (m.isVisible()) {
					Dimension d = m.getPreferredSize();
					dim.height = Math.max(dim.height, d.height);
					if (firstVisibleComponent) {
						firstVisibleComponent = false;
					} else {
						dim.width += hgap;
					}
					dim.width += d.width;
					int baseline = m.getBaseline(d.width, d.height);
					if (baseline >= 0) {
						maxAscent = Math.max(maxAscent, baseline);
						maxDescent = Math.max(maxDescent, d.height - baseline);
					}
				}
			}
			dim.height = Math.max(maxAscent + maxDescent, dim.height);
			Insets insets = target.getInsets();
			dim.width += insets.left + insets.right + hgap * 2;
			dim.height += insets.top + insets.bottom + vgap * 2;
			return dim;
		}
	}

	/**
	 * Returns the minimum dimensions needed to layout the <i>visible</i>
	 * components contained in the specified target container.
	 * 
	 * @param target
	 *            the container that needs to be laid out
	 * @return the minimum dimensions to lay out the subcomponents of the
	 *         specified container
	 */
	public Dimension minimumLayoutSize(Container target) {
		synchronized (target.getTreeLock()) {
			Dimension dim = new Dimension(0, 0);
			int nmembers = target.getComponentCount();
			int maxAscent = 0;
			int maxDescent = 0;
			boolean firstVisibleComponent = true;
			for (int i = 0; i < nmembers; i++) {
				Component m = target.getComponent(i);
				if (m.isVisible()) {
					Dimension d = m.getMinimumSize();
					dim.height = Math.max(dim.height, d.height);
					if (firstVisibleComponent) {
						firstVisibleComponent = false;
					} else {
						dim.width += hgap;
					}
					dim.width += d.width;
					int baseline = m.getBaseline(d.width, d.height);
					if (baseline >= 0) {
						maxAscent = Math.max(maxAscent, baseline);
						maxDescent = Math.max(maxDescent, dim.height - baseline);
					}
				}
			}
			dim.height = Math.max(maxAscent + maxDescent, dim.height);
			Insets insets = target.getInsets();
			dim.width += insets.left + insets.right + hgap * 2;
			dim.height += insets.top + insets.bottom + vgap * 2;
			return dim;
		}
	}

	/**
	 * Centers the elements in the specified row, if there is any slack.
	 * 
	 * @param target
	 *            the component which needs to be moved
	 * @param x
	 *            the x coordinate
	 * @param y
	 *            the y coordinate
	 * @param width
	 *            the width dimensions
	 * @param height
	 *            the height dimensions
	 * @param rowStart
	 *            the beginning of the row
	 * @param rowEnd
	 *            the the ending of the row
	 * @param useBaseline
	 *            Whether or not to align on baseline.
	 * @param ascent
	 *            Ascent for the components. This is only valid if useBaseline
	 *            is true.
	 * @param descent
	 *            Ascent for the components. This is only valid if useBaseline
	 *            is true.
	 * @return actual row height
	 */
	private int moveComponents(Container target, int x, int y, int width, int height, int rowStart, int rowEnd, boolean ltr, int[] ascent,
			int[] descent) {
		switch (align) {
		case CENTER:
			x += width / 2;
			break;
		case LEADING:
			break;
		case TRAILING:
			x += width;
			break;
		}
		int maxAscent = 0;
		int nonbaselineHeight = 0;
		int baselineOffset = 0;
		int maxDescent = 0;
		for (int i = rowStart; i < rowEnd; i++) {
			Component m = target.getComponent(i);
			if (m.isVisible()) {
				if (ascent[i] >= 0) {
					maxAscent = Math.max(maxAscent, ascent[i]);
					maxDescent = Math.max(maxDescent, descent[i]);
				} else {
					nonbaselineHeight = Math.max(m.getHeight(), nonbaselineHeight);
				}
			}
		}
		height = Math.max(maxAscent + maxDescent, nonbaselineHeight);
		baselineOffset = (height - maxAscent - maxDescent) / 2;
		for (int i = rowStart; i < rowEnd; i++) {
			Component m = target.getComponent(i);
			if (m.isVisible()) {
				int cy;
				if (ascent[i] >= 0) {
					cy = y + baselineOffset + maxAscent - ascent[i];
				} else {
					cy = y + (height - m.getHeight()) / 2;
				}
				if (ltr) {
					m.setLocation(x, cy);
				} else {
					m.setLocation(target.getWidth() - x - m.getWidth(), cy);
				}
				x += m.getWidth() + hgap;
			}
		}
		return height;
	}

	/**
	 * Lays out the container. This method lets each <i>visible</i> component
	 * take its preferred size by reshaping the components in the target
	 * container in order to satisfy the alignment of this
	 * <code>FlowLayout</code> object.
	 * 
	 * @param target
	 *            the specified component being laid out
	 * @see Container
	 * @see java.awt.Container#doLayout
	 */
	public void layoutContainer(Container target) {
		synchronized (target.getTreeLock()) {
			Insets insets = target.getInsets();
			int maxwidth = target.getWidth() - (insets.left + insets.right + hgap * 2);
			int nmembers = target.getComponentCount();
			int x = 0, y = insets.top + vgap;
			int rowh = 0, start = 0;
			boolean ltr = target.getComponentOrientation().isLeftToRight();
			int[] ascent = new int[nmembers];
			int[] descent = new int[nmembers];
			for (int i = 0; i < nmembers; i++) {
				Component m = target.getComponent(i);
				if (m.isVisible()) {
					Dimension d = m.getPreferredSize();
					m.setSize(d.width, d.height);
					int baseline = m.getBaseline(d.width, d.height);
					if (baseline >= 0) {
						ascent[i] = baseline;
						descent[i] = d.height - baseline;
					} else {
						ascent[i] = -1;
					}
					if ((x == 0) || ((x + d.width) <= maxwidth)) {
						if (x > 0) {
							x += hgap;
						}
						x += d.width;
						rowh = Math.max(rowh, d.height);
					} else {
						rowh = moveComponents(target, insets.left + hgap, y, maxwidth - x, rowh, start, i, ltr, ascent, descent);
						x = d.width;
						y += vgap + rowh;
						rowh = d.height;
						start = i;
					}
				}
			}
			moveComponents(target, insets.left + hgap, y, maxwidth - x, rowh, start, nmembers, ltr, ascent, descent);
		}
	}

	public String toString() {
		String str = "";
		switch (this.align) {
		case CENTER:
			str = ",align=center";
			break;
		case LEADING:
			str = ",align=leading";
			break;
		case TRAILING:
			str = ",align=trailing";
			break;
		}
		return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + str + "]";
	}
}
